iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
AI Engineering

AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄系列 第 7

Day 7|第一份 Skill:AI Code Review 九面向(下)與兩道閘

  • 分享至 

  • xImage
  •  

簡短回顧

昨天走完了九面向的前五類(A 風格、B 簡潔、C 安全、D API 慣例、E 架構),也講了為什麼在清單之前,要先放一個「沒有清單」的 subagent。

今天把後四類走完,再補上 CodeGraph 這個結構視角、subagent 的命名規約,以及 finding 進報告前的兩道閘:清單外的發現先提問;沒有驗證過的主張不准掛等級。

深度審查的九個面向(續:F~I)

昨天那把尺今天收得更準一點:事實多半通用,★ 標的是我們家的處置:判級、豁免、選型。換一家公司,事實還在,處置要重新談。這四類裡有四條掛 ★。

F 資料取用與資料庫

涵蓋以下項目:

  1. dict.get(k, default) 防不了 None。key 存在但值是 None 時,default 不會生效;若該值會沿關鍵路徑傳播並造成資料錯誤或服務失敗,可列為 Critical。
  2. ★ 砍欄位、改名、換型別,或改掉既有讀者/存量資料所依賴的格式,可能讓仍在使用舊形狀的消費者同時失效(Hyrum's Law)。
  3. 多步寫入沒有 transaction,或 read-modify-write 沒有併發保護。
  4. 任何碰到共享狀態、多呼叫者、多實例的路徑,一律先問一句:兩個請求同時打進來會怎樣?
  5. 多個 Gunicorn worker 或服務副本之間不共享 process 內的記憶體與 lock。若正確性依賴這類共享狀態,應依情境改用資料庫唯一約束、transaction、樂觀/悲觀鎖、冪等設計或分散式鎖。

Hyrum's Law:

當一個 API 或系統擁有足夠多的使用者時,只看明文契約已經不夠:系統所有可被觀察到的行為,都可能被某個使用者當成依賴。

舉例:

API 的契約只寫著:「驗證失敗時會拋出 ValidationError。」

使用者在寫程式碼時,為了判斷細節,直接拿 response body 比對字串:if (err.message.includes("Invalid ID"))。某天你把錯誤訊息從 Invalid ID 改成文法更完整的 The provided ID is invalid,依賴舊字串的流程控制就會壞掉。

比較合適的解法:

  1. Expand:讓新欄位/新格式與舊版本並存,而不是直接取代。
  2. Migrate:backfill 存量資料,必要時雙寫以保持兩邊一致。注意:塞一個 server_default 或 sentinel 常數不等於完成 backfill;那可能只讓 schema 過得去,資料語意仍然是空的。
  3. Contract:確認所有消費者都搬到新形狀之後,才拆掉舊版本。

延伸閱讀:Hyrum's Law

F-1 的出生證明:這條是真的踩過

dict.get(k, default) 防不了 None。這句話寫成一行清單看起來像常識,但它是我們真的摔過才補上的。

情境很單純:key 存在,值是 Nonedefault 只在 key 不存在時才生效,所以它一次都沒有被用上,None 就這樣往下游流。

麻煩的是,修法本身還有第二個陷阱。直覺會改成 dict.get(k) or default,但這個寫法會連 False00.0 一起吃掉,而那些在很多欄位上都是合法值。所以「一律改成 or」也是錯的。

這就是為什麼這條沒辦法寫成機械條文,也沒辦法完全交給 linter。要判斷的是「這個欄位現實上會不會是 None」,以及「它會不會是 0False」;那是情境問題。這一條留在 LLM 這一軌,不在確定性工具那一軌。

G 測試

重點是斷言品質:驗的是「實際行為」,還是只驗「有回傳東西」?

需要提高警覺的例子:

  1. 只驗 status_code == 200,卻不檢查 response body 或應有的副作用。
  2. 只寫 assert x is not None,卻沒有驗證內容、狀態變化或業務契約。
  3. mock 設定回傳 X,最後只驗證拿到 X;如果沒有同時驗證 wiring、參數轉換或互動行為,這類測試通常只是在重述 mock。

這些寫法不是一律錯。x is not None 可能正是在守一條契約;mock 也可以用來驗證呼叫參數與互動次數。要抓的是:測試是否只證明了自己安排的結果,卻沒有證明產品行為。

H 非 Python 檔

依 diff 涉及的檔案類型,額外啟動前端、容器、Web Server、部署設定與 migration 檢查。Alembic migration 不受「是否包含非 Python 檔」限制;只要 diff 中出現 migration,就必須檢查。

  • 多分支 UI 元件:每個互動分支是否都審查到。
  • 渲染狀態完整性:loading、empty、error 是否都有相應的處理與呈現。
  • Vue:v-html 的 XSS 風險、props 驗證、reactivity 正確性。
  • ★ Vue 一律使用 Composition API 與 <script setup>(團隊選型,不是通則)。
  • Dockerfile:base image 是否可信且符合需求、是否安裝多餘工具、是否適合使用 multi-stage build。slimalpine 是選項,不是通用答案。
  • nginx.conf:確認入口 HTML 的快取/重新驗證策略,並讓帶 content hash 的靜態資產使用長效快取。
  • docker-compose:logging、restart policy、volume 掛載路徑是否符合實際部署需求。
  • Alembic migration:與 ORM model 的一致性、破壞性 DDL,以及 rollback、roll-forward 或 recovery 策略。不是每個資料變更都能安全 downgrade,重點是失敗時有沒有可執行的復原方案。

I 回溯分析

修改既有函式時,沿著三個子軸回溯:

  1. 簽名相容性:改簽名後,所有 caller 還相容嗎?
  2. ★ 危險操作的前置條件一致性:面對多個 caller,要列出每個呼叫者的前置條件;若有差異,關鍵是判斷:這個不一致是 bug,還是刻意設計?
  3. 隱性輸入契約:追值,不只追參數名。上游塞進來的值,真的符合這個函式的假設嗎?

「其餘沒有列到的條件,就使用 best practices 進行審查。」

這句是反例 😂。對 AI 說 best practices 等於沒說,它不會因為這句話就看得更準。

而且要老實講,這句話到現在還留在 skill 裡SKILL.md 的 Phase 3 寫著:Anything the checklist does not name is reviewed against general best practices

不是忘了改,是我後來想通它拿不掉:清單不可能窮舉,總得有一句交代「清單沒寫到的怎麼辦」,而那句話再怎麼寫都會泛。

能補的是另一半。不去管它「看什麼」,改成管它「能說什麼」:清單沒有涵蓋的發現,一律先以問句浮出,不准直接掛嚴重度。泛的指令留著,但它吐出來的東西不會自動被當成有依據的判定。

第一道閘:清單外的發現先提問

九面向是我們願意讓 reviewer 主動下判斷的範圍;清單以外不是禁區,但證據門檻不同。

清單外的發現可以提出,卻不能只靠一句「違反 best practices」就掛上 Critical、Suggestion 或 Nit。先把它寫成問題,說清楚觀察到的現象與需要確認的條件;等驗證完成,才升格成 finding。

第一道閘控制的是:AI 在清單之外能說什麼。

CodeGraph:給 Reviewer 的結構視角

九面向講完了,但清單只交代「要看什麼」,沒有交代「怎麼把散在各處的呼叫關係找齊」。尤其是 I 類的回溯分析,只靠 grep 拼湊,很容易漏掉第二、第三層關係。所以收尾之前,先補一個結構視角的工具。

CodeGraph 官方 repository

安裝與初始化

CLI 在每台執行 review 的機器上安裝一次:

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh

我的 skill 會先 clone 本次要 review 的 repository,再於該 repository 執行:

cd /path/to/cloned-repository
codegraph init

codegraph init 才是專案層級的初始化:它會建立 .codegraph/,並在同一步完成第一次建圖。因為每次 review 都是在剛 clone 回來的 repository 上執行,所以這一步放在 clone 之後即可。

另外還有一條很像、但責任完全不同的命令:

codegraph install

它不是建索引,而是把 CodeGraph 的 MCP server 與使用說明接進 Claude Code、Codex、Cursor 等 agent。我的 skill 是透過 shell 直接呼叫 CodeGraph CLI,因此這不是每次 review 的必要步驟;只有要讓 agent 直接呼叫 codegraph_explore 等 MCP tool 時,才需要事先在該環境設定一次。

它建立了什麼

CodeGraph 會掃描 repository,把函式、類別、方法等符號整理成節點,再以 calls、imports、extends、implements 等關係連成圖;部分語言與框架還會有額外的關係解析。

建完之後,原本需要多輪 grep 加上開檔案拼湊的問題,可以改用結構查詢協助回答:

  • explore:取得相關符號的原始碼、呼叫路徑與影響範圍摘要。
  • callers:誰在呼叫這個函式?也就是 fan-in。
  • impact:修改這個符號,可能波及哪些地方?也就是 blast radius。

若走 MCP,現行版本主要透過 codegraph_explore 提供查詢;我的 skill 走 CLI,所以可以直接使用 codegraph callerscodegraph impact 等命令。

不是取代 grep,而是交叉驗證。

CodeGraph 是導航加速器,不是完整性的最終裁決。靜態圖可能受到動態派發、反射、依賴注入、框架慣例、產生碼與索引狀態影響;grep 也可能因搜尋範圍、關鍵字選擇或忽略檔案而漏掉引用。

所以分工規則是:

  • 結構性、遞移性的問題,例如「這個改動碰到哪裡」、「誰呼叫這個符號」、「改簽名可能讓誰壞掉」:先用 CodeGraph 建立路徑,再回到實際程式碼驗證。
  • 文字層級與枚舉性的問題,例如「設定值在哪些地方出現」、「還有沒有舊名稱」、「是不是少了一個 decorator」:用 grep 補查所有直接命中,再逐一排除誤報。

兩種工具一起使用,可以降低單一工具造成的盲區,但不能保證零誤報或零遺漏。在醫療系統的 review 裡,我們追求的不是宣稱「一定不漏」,而是讓遺漏風險可控,並讓每一條 finding 都有可重現的證據。

這也呼應整套 skill 的工具觀:引入工具之前,先把它「看不見什麼」寫清楚,再決定它能負責什麼。CodeGraph 在我的文件裡,盲區清單比功能清單還長。這不是貶低它,而是能放心使用它的前提。

關於 subagent

  1. 確定性工具會交給 subagent 執行掃描與匯總。我自己使用的版本指定為 Sonnet,但公開出去的 agent 檔沒有寫死 model:

    這不是硬規則。同一份 skill 不只會在 Claude Code 上執行,Codex 也會使用;把型號寫死,就會讓它綁在特定工具或模型命名上。如果確定只在 Claude Code 裡使用,明確指定模型也沒有問題。

  2. 因為後續會追蹤分析流程,所以 subagent 的 subagent_type 採用 ncr-* 格式。沒有安裝這些 agent 檔的環境,skill 會退回 general-purpose subagent,把對應的 agents/ncr-*.md 整份當 prompt 傳入第一行的 ncr-* 標記照樣存在,追蹤不會斷。

  3. 我也會在交給 subagent 的 prompt 第一行放上對應的 ncr-* 標記。

ncrnathan-code-review 的縮寫。未來分析 Claude Code 的 JSONL transcript 時,可以直接 grep 這個模式,定位各個 subagent 的進度與當下程序。

第二道閘:沒驗證的主張不准掛等級

AI reviewer 最常見的錯誤型態,是自信滿滿地宣稱「這裡少了驗證」或「這段被刪掉會壞」,但那段邏輯其實只是搬到了同一個 diff 的別處。

所以我立了一條硬規則:任何宣稱「缺了什麼」或「某條路徑會壞」的 finding,寫進報告前都必須先找過反證:

  • 缺少的東西是不是移到了別處?
  • 所謂會壞的路徑,上游是不是已經擋掉了?
  • 問題成立所需的輸入、部署方式或併發條件,真的存在嗎?

找過反證後仍然成立,才能掛上 Critical、Suggestion 或 Nit 進入報告。無法驗證的內容,不准掛等級,只能以「提問」形式浮出,並明確標示尚未驗證。

第一道閘管清單外的發現,第二道閘管所有 finding 的證據。兩道閘守的是同一件事:不要讓 AI 的語氣比證據更有自信。

誤報真正殺傷的是信任。 報告只要被抓到一次自信的錯,後面的 Critical 就不會再有人認真看了。

本日小結

九個面向到今天走完了。

回頭看,這份清單真正花時間的,不是「列出檢查項目」,而是替每一項標出嚴重度、寫下豁免情境,以及決定衝突時誰優先。那些才是「像我」的部分;清單本身反而最容易抄。

中間的 CodeGraph 也不是單純「多一個工具」,而是示範了引入工具的順序:先寫清楚它看不見什麼,再決定它能負責什麼。

最後再用兩道閘把輸出收住:清單外的發現先提問;所有 finding 都必須驗證並找過反證,才能掛上等級。

Day 1 那張表裡的「以正合,以奇勝」,到今天兩邊才算到齊:正是 Day 5 那些確定性工具,奇是這兩天的九面向。

明天進入最後一段規劃:報告要長什麼樣、怎麼交付、怎麼發佈出去。


上一篇
Day 6|第一份 Skill:盲讀、入口枚舉閘與九面向(上)
系列文
AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言